// 版权所有2011 Go作者。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package template

import (
	"fmt"
	"io"
	"io/fs"
	"os"
	"path"
	"path/filepath"
	"sync"
	"text/template"
	"text/template/parse"
)

// 模板是“text/Template”中的一个专门模板，它生成一个安全的
// HTML文档片段。
type Template struct {
	// 如果转义失败，则为粘滞错误，如果转义成功，则为转义OK。
	escapeErr error
	// 我们可以嵌入文本/模板字段，但不嵌入更安全，因为
	// 我们需要保持名称空间的版本与基础
	// 模板的同步。
	text *template.Template
	// 基础模板的解析树，已更新为HTML安全。
	Tree       *parse.Tree
	*nameSpace // 所有关联模板通用
}

// escapeOK是用于指示有效转义的前哨值。
var escapeOK = fmt.Errorf("template escaped correctly")

// 名称空间是关联中所有模板共享的数据结构。
type nameSpace struct {
	mu      sync.Mutex
	set     map[string]*Template
	escaped bool
	esc     escaper
}

// Templates返回与t相关联的模板片段，包括t 
// 本身。
func (t *Template) Templates() []*Template {
	ns := t.nameSpace
	ns.mu.Lock()
	defer ns.mu.Unlock()
	// 返回一个切片，这样我们就不会暴露地图。
	m := make([]*Template, 0, len(ns.set))
	for _, v := range ns.set {
		m = append(m, v)
	}
	return m
}

// 选项设置模板的选项。选项由
// 字符串描述，可以是简单字符串，也可以是“key=value”。在
// 选项字符串中最多有一个等号。如果选项字符串
// 无法识别或无效，则选项会失效。
// 
// 已知选项：
// 
// missingkey:如果映射是
// 使用映射中不存在的密钥进行索引，则控制执行期间的行为。
// “missingkey=default”或“missingkey=invalid”
// /默认行为：不执行任何操作并继续执行。
// 如果打印，索引操作的结果是字符串
// “<no value>”。
// “missingkey=zero”
// 该操作返回映射类型元素的零值。
// “missingkey=error”
// 执行会立即停止，出现错误。
// 
func (t *Template) Option(opt ...string) *Template {
	t.text.Option(opt...)
	return t
}

// checkCanParse检查是否可以解析模板。
// 如果不是，则返回一个错误。
func (t *Template) checkCanParse() error {
	if t == nil {
		return nil
	}
	t.nameSpace.mu.Lock()
	defer t.nameSpace.mu.Unlock()
	if t.nameSpace.escaped {
		return fmt.Errorf("html/template: cannot Parse after Execute")
	}
	return nil
}

// 转义将转义所有关联的模板。
func (t *Template) escape() error {
	t.nameSpace.mu.Lock()
	defer t.nameSpace.mu.Unlock()
	t.nameSpace.escaped = true
	if t.escapeErr == nil {
		if t.Tree == nil {
			return fmt.Errorf("template: %q is an incomplete or empty template", t.Name())
		}
		if err := escapeTemplate(t, t.text.Root, t.Name()); err != nil {
			return err
		}
	} else if t.escapeErr != escapeOK {
		return t.escapeErr
	}
	return nil
}

// Execute将已解析的模板应用于指定的数据对象，
// 将输出写入wr。
// 如果执行模板或写入其输出时发生错误，
// 执行将停止，但部分结果可能已写入输出编写器。
// 模板可以安全地并行执行，但如果并行
// 执行共享一个写入程序，则输出可能会交错。
func (t *Template) Execute(wr io.Writer, data any) error {
	if err := t.escape(); err != nil {
		return err
	}
	return t.text.Execute(wr, data)
}

// ExecuteTemplate将与具有给定
// 名称的t关联的模板应用于指定的数据对象，并将输出写入wr。
// 如果执行模板或写入其输出时发生错误，
// 执行将停止，但部分结果可能已写入输出编写器
// 中。
// 模板可以安全地并行执行，但如果并行
// 执行共享一个写入程序，则输出可能会交错。
func (t *Template) ExecuteTemplate(wr io.Writer, name string, data any) error {
	tmpl, err := t.lookupAndEscapeTemplate(name)
	if err != nil {
		return err
	}
	return tmpl.text.Execute(wr, data)
}

// lookupAndEscapeTemplate保证对名为
// 的模板进行转义，如果无法转义，则返回错误。它返回命名的
// 模板。
func (t *Template) lookupAndEscapeTemplate(name string) (tmpl *Template, err error) {
	t.nameSpace.mu.Lock()
	defer t.nameSpace.mu.Unlock()
	t.nameSpace.escaped = true
	tmpl = t.set[name]
	if tmpl == nil {
		return nil, fmt.Errorf("html/template: %q is undefined", name)
	}
	if tmpl.escapeErr != nil && tmpl.escapeErr != escapeOK {
		return nil, tmpl.escapeErr
	}
	if tmpl.text.Tree == nil || tmpl.text.Root == nil {
		return nil, fmt.Errorf("html/template: %q is an incomplete template", name)
	}
	if t.text.Lookup(name) == nil {
		panic("html/template internal error: template escaping out of sync")
	}
	if tmpl.escapeErr == nil {
		err = escapeTemplate(tmpl, tmpl.text.Root, name)
	}
	return tmpl, err
}

// DefinedTemplates返回一个列有已定义模板的字符串，
// 前缀为字符串“defined templates are:”。如果没有，则返回空字符串。用于生成错误消息。
func (t *Template) DefinedTemplates() string {
	return t.text.DefinedTemplates()
}

// Parse将文本解析为t的模板体。
// 命名模板定义（{define…}）或者{block…}语句）文本
// 定义与t相关的其他模板，并从
// t本身的定义中删除。
// 
// 在第一次使用Execute on t或任何相关模板之前，可以在连续调用Parse、
// 中重新定义模板。
// 如果模板定义的正文仅包含空格和注释，则该模板定义将被视为空，不会替换现有模板的正文。
// 这允许使用Parse添加新的命名模板定义，而无需覆盖模板主体。
func (t *Template) Parse(text string) (*Template, error) {
	if err := t.checkCanParse(); err != nil {
		return nil, err
	}

	ret, err := t.text.Parse(text)
	if err != nil {
		return nil, err
	}

	// 通常，所有命名的模板都可能在脚下发生了更改。
	// 不管怎样，可能已经定义了一些新的。
	// 模板。模板集已更新；更新我们的。
	t.nameSpace.mu.Lock()
	defer t.nameSpace.mu.Unlock()
	for _, v := range ret.Templates() {
		name := v.Name()
		tmpl := t.set[name]
		if tmpl == nil {
			tmpl = t.new(name)
		}
		tmpl.text = v
		tmpl.Tree = v.Tree
	}
	return t, nil
}

// AddParseTree创建一个名为和parse tree 
// 并将其与t关联。
// 
// 如果已执行t或任何关联模板，则返回错误。
func (t *Template) AddParseTree(name string, tree *parse.Tree) (*Template, error) {
	if err := t.checkCanParse(); err != nil {
		return nil, err
	}

	t.nameSpace.mu.Lock()
	defer t.nameSpace.mu.Unlock()
	text, err := t.text.AddParseTree(name, tree)
	if err != nil {
		return nil, err
	}
	ret := &Template{
		nil,
		text,
		text.Tree,
		t.nameSpace,
	}
	t.set[name] = ret
	return ret, nil
}

// 克隆返回模板的副本，包括所有关联的
// 模板。实际的表示形式没有被复制，但是
// 关联模板的名称空间是，因此在副本中进一步调用Parse会将
// 模板添加到副本中，但不会添加到原始模板中。克隆可用于准备
// 通用模板，并在克隆完成后添加变体，将其与其他模板的变体定义
// 一起使用。
// 
// 如果已经执行了t，则返回一个错误。
func (t *Template) Clone() (*Template, error) {
	t.nameSpace.mu.Lock()
	defer t.nameSpace.mu.Unlock()
	if t.escapeErr != nil {
		return nil, fmt.Errorf("html/template: cannot Clone %q after it has executed", t.Name())
	}
	textClone, err := t.text.Clone()
	if err != nil {
		return nil, err
	}
	ns := &nameSpace{set: make(map[string]*Template)}
	ns.esc = makeEscaper(ns)
	ret := &Template{
		nil,
		textClone,
		textClone.Tree,
		ns,
	}
	ret.set[ret.Name()] = ret
	for _, x := range textClone.Templates() {
		name := x.Name()
		src := t.set[name]
		if src == nil || src.escapeErr != nil {
			return nil, fmt.Errorf("html/template: cannot Clone %q after it has executed", t.Name())
		}
		x.Tree = x.Tree.Copy()
		ret.set[name] = &Template{
			nil,
			x,
			x.Tree,
			ret.nameSpace,
		}
	}
	// 返回与此模板名称关联的模板。
	return ret.set[ret.Name()], nil
}

// New用给定的名称分配一个新的HTML模板。
func New(name string) *Template {
	ns := &nameSpace{set: make(map[string]*Template)}
	ns.esc = makeEscaper(ns)
	tmpl := &Template{
		nil,
		template.New(name),
		nil,
		ns,
	}
	tmpl.set[name] = tmpl
	return tmpl
}

// New分配一个新的HTML模板，该模板与给定的模板
// 关联，并使用相同的分隔符。关联是可传递的，
// 允许一个模板通过{template}操作调用另一个模板。
// 
// 如果已经存在具有给定名称的模板，则新的HTML模板
// 将替换它。现有模板将被重置并与
// t解除关联。
func (t *Template) New(name string) *Template {
	t.nameSpace.mu.Lock()
	defer t.nameSpace.mu.Unlock()
	return t.new(name)
}

// new是new的实现，没有锁。
func (t *Template) new(name string) *Template {
	tmpl := &Template{
		nil,
		t.text.New(name),
		nil,
		t.nameSpace,
	}
	if existing, ok := tmpl.set[name]; ok {
		emptyTmpl := New(existing.Name())
		*existing = *emptyTmpl
	}
	tmpl.set[name] = tmpl
	return tmpl
}

// Name返回模板的名称。
func (t *Template) Name() string {
	return t.text.Name()
}

// FuncMap是定义从名称到
// 函数映射的映射类型。每个函数必须有一个返回值，或者有两个
// 返回值，第二个返回值的类型为error。在这种情况下，如果
// 第二个（错误）参数在执行过程中的计算结果为非零，则执行
// 终止并返回该错误。FuncMap与“文本/模板”中的FuncMap具有相同的基类型
// ，复制到此处，因此客户端无需导入
// “文本/模板”。
type FuncMap map[string]any

// Funcs将参数映射的元素添加到模板的函数映射中。
// 必须在解析模板之前调用它。
// 如果映射中的值不是具有适当返回的函数，则会出现恐慌。但是，覆盖地图的元素是合法的。返回值
// 是模板，因此可以链接调用。
func (t *Template) Funcs(funcMap FuncMap) *Template {
	t.text.Funcs(template.FuncMap(funcMap))
	return t
}

// Delims将操作分隔符设置为指定的字符串，用于
// 后续的Parse、ParseFiles或ParseGlob调用。嵌套模板
// 定义将继承设置。空分隔符代表
// 对应的默认值：{{or}。
// 返回值是模板，因此可以链接调用。
func (t *Template) Delims(left, right string) *Template {
	t.text.Delims(left, right)
	return t
}

// Lookup返回与t、
// 关联的具有给定名称的模板，如果没有此类模板，则返回nil。
func (t *Template) Lookup(name string) *Template {
	t.nameSpace.mu.Lock()
	defer t.nameSpace.mu.Unlock()
	return t.set[name]
}

// 必须是一个帮助程序，它包装对返回（*模板，错误）
// 的函数的调用，如果错误为非零，则会惊慌失措。它用于变量初始化
// 例如
// var t=template。必须（模板。新（“名称”）。Parse（“html”））
func Must(t *Template, err error) *Template {
	if err != nil {
		panic(err)
	}
	return t
}

// ParseFiles创建一个新模板，并从命名文件中解析模板定义。返回的模板名称将包含第一个文件的（基本）名称和
// /（已解析）内容。必须至少有一个文件。
// 如果发生错误，解析将停止，返回的*模板为零。
// 
// 当解析不同目录中具有相同名称的多个文件时，
// 最后一个提到的将是结果。
// 例如，ParseFiles（“a/foo”，“b/foo”）将“b/foo”存储为模板
// 命名为“foo”，而“a/foo”不可用。
func ParseFiles(filenames ...string) (*Template, error) {
	return parseFiles(nil, readFileOS, filenames...)
}

// ParseFiles解析命名文件，并将生成的模板与
// t关联。如果出现错误，解析将停止，返回的模板为零；
// 否则为t。必须至少有一个文件。
// 
// 当解析不同目录中具有相同名称的多个文件时，
// 最后一个提到的将是结果。
// 
// ParseFiles如果已执行t或任何关联模板，则返回错误。
func (t *Template) ParseFiles(filenames ...string) (*Template, error) {
	return parseFiles(t, readFileOS, filenames...)
}

// parseFiles是方法和函数的助手。如果参数
// template为nil，则它是从第一个文件创建的。
func parseFiles(t *Template, readFile func(string) (string, []byte, error), filenames ...string) (*Template, error) {
	if err := t.checkCanParse(); err != nil {
		return nil, err
	}

	if len(filenames) == 0 {
		// 其实不是问题，但要始终如一。
		return nil, fmt.Errorf("html/template: no files named in call to ParseFiles")
	}
	for _, filename := range filenames {
		name, b, err := readFile(filename)
		if err != nil {
			return nil, err
		}
		s := string(b)
		// 如果尚未定义，第一个模板将成为返回值，
		// 我们在后续的新调用中使用这个函数来关联
		// 所有模板。此外，如果该文件与t同名，则该文件将成为t的内容，因此
		// t，err:=New（name）。Funcs（xxx）。ParseFiles（名称）
		// 有效。否则，我们将创建一个与t关联的新模板。
		var tmpl *Template
		if t == nil {
			t = New(name)
		}
		if name == t.Name() {
			tmpl = t
		} else {
			tmpl = t.New(name)
		}
		_, err = tmpl.Parse(s)
		if err != nil {
			return nil, err
		}
	}
	return t, nil
}

// ParseGlob将创建一个新模板，并解析模式标识的文件中的模板定义。根据文件路径的
// 语义匹配文件。匹配，并且模式必须至少匹配一个文件。
// 返回的模板将包含模式匹配的第一个文件的（基本）名称和（已解析）内容。ParseGlob相当于调用
// ParseFiles，其中包含与模式匹配的文件列表。
// 
// 当解析不同目录中具有相同名称的多个文件时，
// 最后一个提到的将是结果。
func ParseGlob(pattern string) (*Template, error) {
	return parseGlob(nil, pattern)
}

// ParseGlob解析
// 模式标识的文件中的模板定义，并将生成的模板与t关联。根据文件路径的语义，这些文件是匹配的
// 文件。匹配，并且模式必须在
// 至少一个文件处匹配。ParseGlob相当于使用
// 模式匹配的文件列表调用t.ParseFiles。
// 
// 当解析不同目录中具有相同名称的多个文件时，
// 最后一个提到的将是结果。
// 
// ParseGlob如果已经执行了t或任何相关模板，则返回一个错误。
func (t *Template) ParseGlob(pattern string) (*Template, error) {
	return parseGlob(t, pattern)
}

// parseGlob是parseGlob函数和方法的实现。
func parseGlob(t *Template, pattern string) (*Template, error) {
	if err := t.checkCanParse(); err != nil {
		return nil, err
	}
	filenames, err := filepath.Glob(pattern)
	if err != nil {
		return nil, err
	}
	if len(filenames) == 0 {
		return nil, fmt.Errorf("html/template: pattern matches no files: %#q", pattern)
	}
	return parseFiles(t, readFileOS, filenames...)
}

// IsTrue报告值是否为“真”，而不是其类型的零，
// 以及该值是否具有有意义的真值。这是国际单项体育联合会和其他类似行动使用的事实的定义。
func IsTrue(val any) (truth, ok bool) {
	return template.IsTrue(val)
}

// ParseFS类似于ParseFiles或ParseGlob，但从文件系统fs 
// 而不是主机操作系统的文件系统读取。
// 它接受一系列全局模式。wen jian wen jian defg
func ParseFS(fs fs.FS, patterns ...string) (*Template, error) {
	return parseFS(nil, fs, patterns)
}

// ParseFS类似于ParseFiles或ParseGlob，但从文件系统fs 
// 而不是主机操作系统的文件系统读取。
// 它接受一系列全局模式。
// /（请注意，大多数文件名用作仅匹配自身的全局模式。）
func (t *Template) ParseFS(fs fs.FS, patterns ...string) (*Template, error) {
	return parseFS(t, fs, patterns)
}

func parseFS(t *Template, fsys fs.FS, patterns []string) (*Template, error) {
	var filenames []string
	for _, pattern := range patterns {
		list, err := fs.Glob(fsys, pattern)
		if err != nil {
			return nil, err
		}
		if len(list) == 0 {
			return nil, fmt.Errorf("template: pattern matches no files: %#q", pattern)
		}
		filenames = append(filenames, list...)
	}
	return parseFiles(t, readFileFS(fsys), filenames...)
}

func readFileOS(file string) (name string, b []byte, err error) {
	name = filepath.Base(file)
	b, err = os.ReadFile(file)
	return
}

func readFileFS(fsys fs.FS) func(string) (string, []byte, error) {
	return func(file string) (name string, b []byte, err error) {
		name = path.Base(file)
		b, err = fs.ReadFile(fsys, file)
		return
	}
}
